<?php
namespace Swork\Pool\ElasticSearch;

use Swork\Exception\ElasticSearchException;
use Swork\Helper\HttpHelper;
use Swork\Pool\AbstractConnection;
use Swork\Pool\ConfigInterface;

/**
 * Interface ConnectInterface
 * @package Swoft\Pool
 */
class ElasticSearchConnection extends AbstractConnection
{
    /**
     * 连接配置
     * @var array
     */
    private $url;

    /**
     * CURL配置参数
     * @var array
     */
    private $options = [];

    /**
     * ES配置
     * @var ElasticSearchConfig
     */
    private $conf;

    /**
     * 初始化
     * ElasticSearchConnection constructor.
     * @param ConfigInterface $config
     */
    public function __construct(ConfigInterface $config)
    {
        parent::__construct($config);
        $this->conf = $config;
    }

    /**
     * Create connectioin
     * @return void
     * @throws
     */
    public function create()
    {
        $uri = $this->conf->getUri();

        //主机
        $host = strtolower($uri['host']);
        if (substr($host, 0, 4) != 'http')
        {
            $host = "http://$host";
        }
        $host = trim($host);
        $host = trim($host, '/\\');

        //端口
        $port = '';
        if (strpos(':', $host) === false)
        {
            $port = $uri['port'] ?? '';
            if ($port == '')
            {
                $port = '9200';
            }
            $port = ':' . $port;
        }

        //合成地址
        $this->url = $host . $port . '/';

        //CURL参数
        $this->options = [
            'header' => [
                'Content-Type: application/json'
            ],
            'json' => true
        ];

        //超时
        $timeout = $this->conf->getTimeout();
        if ($timeout > 0)
        {
            $this->options['timeout'] = $timeout;
        }
    }

    /**
     * 重新连接
     * @throws
     */
    public function reconnect()
    {
        $this->create();
    }

    /**
     * 检查是否连接中
     * @return bool
     */
    public function check(): bool
    {
        return true;
    }

    /**
     * 检查储存库是否存在
     * @param string $index 储存库名
     * @return array|mixed
     * @throws
     */
    public function indexExist(string $index)
    {
        //检查参数
        $this->checkIndexIsEmpty($index);

        //组装地址
        $url = $this->url . $index;

        //连接获取数据
        $result = HttpHelper::get($url, null, $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //检查错误码
        if (isset($result['error']) && $result['status'] == 404)
        {
            return false;
        }

        //返回
        return true;
    }

    /**
     * 创建储存库
     * @param string $index 储存库名
     * @param array $properties 字段属性
     * @return bool
     * @throws
     */
    public function indexCreate(string $index, array $properties)
    {
        //检查参数
        $this->checkIndexIsEmpty($index);

        //组装地址
        $url = $this->url . $index;

        //组装参数
        $params = [
            'mappings' => [
                'properties' => $properties
            ]
        ];

        //连接获取数据
        $result = HttpHelper::put($url, json_encode($params, JSON_UNESCAPED_UNICODE), $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 创建单个文档索引
     * @param string $index 储存库
     * @param array $values 字段内容
     * @return array|mixed
     * @throws
     */
    public function documentInsert(string $index, array $values)
    {
        //检查参数
        $this->checkIndexIsEmpty($index);

        //组装地址
        $url = $this->url . $index . '/_doc';

        //连接获取数据
        $result = HttpHelper::post($url, json_encode($values, JSON_UNESCAPED_UNICODE), $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 通过搜索条件更新文档内容
     * @param string $index 储存库
     * @param array $query 搜索条件
     * @param array $script 更新脚本内容（按painless语法）
     * @return array|mixed
     * @throws
     */
    public function documentUpdate(string $index, array $query, array $script)
    {
        //检查参数
        $this->checkIndexIsEmpty($index);

        //更新状态之下不允许为条件
        if (count($query['where']) == 0)
        {
            throw new ElasticSearchException('where condition cant not be empty', 301);
        }

        //组装地址
        $url = $this->url . $index . '/_update_by_query';

        //组装参数
        $params = [
            'script' => $script,
            'query' => $query['where']
        ];

        //如果有设置最小匹配分值
        if ($query['min_score'] > 0)
        {
            $params['min_score'] = $query['min_score'];
        }

        //连接获取数据
        $result = HttpHelper::post($url, json_encode($params, JSON_UNESCAPED_UNICODE), $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 通过数据ID更新文档内容
     * @param string $index 储存库
     * @param string $id 数据ID
     * @param array $script 更新脚本内容（按painless语法）
     * @return array|mixed
     * @throws
     */
    public function documentUpdateById(string $index, string $id, array $script)
    {
        //检查参数
        $this->checkIndexIsEmpty($index);
        $this->checkIdIsEmpty($id);

        //组装地址
        $url = $this->url . $index . '/_update/' . $id;

        //组装参数
        $params = [
            'script' => $script,
        ];

        //连接获取数据
        $result = HttpHelper::post($url, json_encode($params, JSON_UNESCAPED_UNICODE), $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 通过数据ID删除文档内容
     * @param string $index 储存库
     * @param string $id 数据ID
     * @return array|mixed
     * @throws
     */
    public function documentDeleteById(string $index, string $id)
    {
        //检查参数
        $this->checkIndexIsEmpty($index);
        $this->checkIdIsEmpty($id);

        //组装地址
        $url = $this->url . $index . '/_doc/' . $id;

        //连接获取数据
        $result = HttpHelper::delete($url, null, $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 通过数据ID获取文档内容
     * @param string $index 储存库
     * @param string $id 数据ID
     * @param string $cols 输出字段
     * @return array|mixed
     * @throws
     */
    public function documentFetchById(string $index, string $id, string $cols = '*')
    {
        //检查参数
        $this->checkIndexIsEmpty($index);
        $this->checkIdIsEmpty($id);

        //组装地址
        $url = $this->url . $index . '/_doc/' . $id;

        //输出字段
        if ($cols != '*' && $cols != '')
        {
            $url .= '?_source_includes=' . $cols;
        }

        //连接获取数据
        $result = HttpHelper::get($url, null, $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 批量操作（如同时操作创建多个文档、批量删除等）
     * @param string $index 储存库
     * @param array $params 批量操作参数
     * @return array|mixed
     * @throws
     */
    public function documentBulk(string $index, array $params)
    {
        //检查参数
        $this->checkIndexIsEmpty($index);

        //组装地址
        $url = $this->url . $index . '/_bulk';

        //连接获取数据
        $result = HttpHelper::post($url, join("\n", $params), $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 中文分词分析
     * @param string $word 需要检验的词
     * @param string $analyzer 分词组件名
     * @return array|mixed
     * @throws
     */
    public function analyze(string $word, string $analyzer)
    {
        $url = $this->url . '_analyze';

        //组装参数
        $params = [
            'analyzer' => $analyzer,
            'text' => $word
        ];

        //连接获取数据
        $data = '';
        if (count($params) > 0)
        {
            $data = json_encode($params, JSON_UNESCAPED_UNICODE);
        }
        $result = HttpHelper::post($url, $data, $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 搜索
     * @param string $index 储存库（为空时，搜索整个库）
     * @param array $query 搜索条件
     * @param string $cols 输出字段
     * @param array $sort 排序字段（暂时无用）
     * @param int $size 每页数量
     * @param int $idx 页码
     * @return array|mixed
     * @throws
     */
    public function search(string $index, array $query, string $cols = '*', array $sort = [], int $size = 25, int $idx = 1)
    {
        //组装地址
        if ($index != '')
        {
            $index = $index . '/';
        }
        $url = $this->url . $index . '_search';

        //输出字段
        if ($cols != '*' && $cols != '')
        {
            $url .= '?_source_includes=' . $cols;
        }

        //组装参数
        $params = [];
        if ($size > 0)
        {
            $params['size'] = $size;
        }
        if ($idx > 0 && $size > 0)
        {
            $params['from'] = ($idx - 1) * $size;
        }
        if (count($query['where']) > 0)
        {
            $params['query'] = $query['where'];
        }
        if ($query['min_score'] > 0)
        {
            $params['min_score'] = $query['min_score'];
        }

        //连接获取数据
        $data = '';
        if (count($params) > 0)
        {
            $data = json_encode($params, JSON_UNESCAPED_UNICODE);
        }
        $result = HttpHelper::post($url, $data, $this->options);
        if ($result == false)
        {
            $this->throwNoReturnError();
        }

        //返回
        return $result;
    }

    /**
     * 检查储存库是否为空
     * @param string $index
     * @throws ElasticSearchException
     */
    private function checkIndexIsEmpty(string $index)
    {
        if ($index == '')
        {
            throw new ElasticSearchException('index不能为空', 398);
        }
    }

    /**
     * 检查数据ID是否为空
     * @param string $id
     * @throws ElasticSearchException
     */
    private function checkIdIsEmpty(string $id)
    {
        if ($id == '')
        {
            throw new ElasticSearchException('数据ID不能为空', 397);
        }
    }

    /**
     * 提示没有正确返回的错误
     * @throws ElasticSearchException
     */
    private function throwNoReturnError()
    {
        throw new ElasticSearchException('调用查询接口错误：无返回正确数据格式', 399);
    }
}
